Phase 3: Data Visualization¶

Now that we have explored and transformed the data, it’s time to visualize the story hidden within it.

Visualization helps us identify:¶

📈 Trends and patterns 🔗 Relationships between variables ⚠️ Outliers and unusual behaviour 💡 Business insights

In this phase, we’ll move from basic distributions to advanced visualizations and learn not just how to create charts, but how to interpret what they are telling us.

A good visualization doesn’t just show data—it helps us understand it.

In [11]:
import matplotlib.pyplot as plt
import seaborn as sns

sns.set(style="whitegrid")
plt.rcParams.update({'font.size': 10})  # change 10 → whatever you prefer
In [12]:
import pandas as pd

df = pd.read_csv('Online Retail Phase 2 Output.csv',index_col = 0)
df.head()
Out[12]:
CustomerID InvoiceNo StockCode Description Quantity InvoiceDate UnitPrice Country Revenue Year Month Day Hour
0 17850 536365 85123A WHITE HANGING HEART T-LIGHT HOLDER 6 2010-12-01 08:26:00 2.55 United Kingdom 15.30 2010 12 1 8
1 17850 536365 71053 WHITE METAL LANTERN 6 2010-12-01 08:26:00 3.39 United Kingdom 20.34 2010 12 1 8
2 17850 536365 84406B CREAM CUPID HEARTS COAT HANGER 8 2010-12-01 08:26:00 2.75 United Kingdom 22.00 2010 12 1 8
3 17850 536365 84029G KNITTED UNION FLAG HOT WATER BOTTLE 6 2010-12-01 08:26:00 3.39 United Kingdom 20.34 2010 12 1 8
4 17850 536365 84029E RED WOOLLY HOTTIE WHITE HEART. 6 2010-12-01 08:26:00 3.39 United Kingdom 20.34 2010 12 1 8

Let's understand boxplot for quantity¶

In [13]:
plt.figure(figsize=(5,5))
sns.boxplot(x=df['Quantity'])
plt.title("Box Plot of Quantity")
plt.show()

NOTE :¶

I plotted the data… and something looked off.”

Extreme outliers were completely distorting the visualization.

That’s when it clicked— visualization isn’t the end of cleaning, it’s part of it.

So instead of trusting the first plot, let’s remove outliers and plot again to see the difference.

Cleaning Required¶

In [14]:
# Function to remove outliers using IQR
def remove_outliers(df, col):
    Q1 = df[col].quantile(0.25)
    Q3 = df[col].quantile(0.75)
    IQR = Q3 - Q1

    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR

    return df[(df[col] >= lower_bound) & (df[col] <= upper_bound)]

# Apply on Quantity and UnitPrice
df_clean = remove_outliers(df, 'Quantity')
df_clean = remove_outliers(df_clean, 'UnitPrice')

Visualizing Quantity Using Boxplot¶

In [15]:
import matplotlib.pyplot as plt
import seaborn as sns

# Style for better visuals
sns.set_style("whitegrid")
sns.set_context("talk")

plt.figure(figsize=(8,4))

# Quantity - Before vs After
plt.subplot(1, 2, 1)
sns.boxplot(y=df['Quantity'], color='lightcoral')
plt.title("Before Cleaning")

plt.subplot(1, 2, 2)
sns.boxplot(y=df_clean['Quantity'], color='seagreen')
plt.title("After Cleaning")

plt.suptitle("Impact of Outlier Removal on Quantity", fontsize=14)
plt.tight_layout()
plt.show()


# Unit Price - Before vs After
plt.figure(figsize=(8,4))

plt.subplot(1, 2, 1)
sns.boxplot(y=df['UnitPrice'], color='orange')
plt.title("Before Cleaning")

plt.subplot(1, 2, 2)
sns.boxplot(y=df_clean['UnitPrice'], color='skyblue')
plt.title("After Cleaning")

plt.suptitle("Impact of Outlier Removal on Unit Price", fontsize=14)
plt.tight_layout()
plt.show()

Conclusion :¶

  • Outlier removal significantly reduced the extreme values in Quantity, making its distribution more compact and representative.
  • For Unit Price, extreme values were substantially reduced, though some higher-priced outliers still remain after cleaning.

Revenue Distribution¶

In [16]:
plt.figure(figsize=(8,5))
sns.histplot(df_clean['Quantity'] * df_clean['UnitPrice'], bins=50)
plt.title("Revenue Distribution (Cleaned Data)")
plt.xlabel("Revenue")
plt.ylabel("Frequency")
plt.show()

Conclusion :¶

  • A small number of transactions show very high revenue values, indicating potential high-value customers or outliers.
  • Further analysis of these high-revenue cases may help identify key revenue drivers and customer segments.

Top 10 Countries by Sales Volume¶

In [17]:
top_countries = df_clean.groupby('Country')['Quantity'].sum().sort_values(ascending=False).head(10)

plt.figure(figsize=(10,5))
top_countries.plot(kind='bar')
plt.title("Top 10 Countries by Sales Volume")
plt.ylabel("Quantity Sold")
plt.xticks(rotation=45)
plt.show()

Conlcusion :¶

  • The UK dominates sales volume by a very large margin, contributing far more than the other countries.
  • Germany and France are the next major markets, while the remaining countries contribute relatively small volumes.

Monthly Sales Trend¶

In [18]:
df_clean['InvoiceDate'] = pd.to_datetime(df_clean['InvoiceDate'])
df_clean['Month'] = df_clean['InvoiceDate'].dt.to_period('M')

monthly_sales = df_clean.groupby('Month')['Quantity'].sum()

plt.figure(figsize=(7,5))
monthly_sales.plot()
plt.title("Monthly Sales Trend")
plt.ylabel("Quantity")
plt.xticks(rotation=45)
plt.show()

Conclusion :¶

  • Sales volume remained relatively steady between 125,000 and 185,000 units from December through August.

  • A dramatic surge began in September, reaching a peak near 370,000 units in November, likely driven by Q4 holiday season demand.

  • Sales dropped sharply back down to around 100,000 units in December, indicating the end of the end-of-year buying cycle.

Unit Price Distribution by Country¶

In [19]:
top_countries = df_clean['Country'].value_counts().head(5).index
df_top = df_clean[df_clean['Country'].isin(top_countries)]

plt.figure(figsize=(10,5))
sns.violinplot(x='Country', y='UnitPrice', data=df_top)
plt.xticks(rotation=45)
plt.title("Unit Price Distribution by Country")
plt.show()

Conclusion :¶

  • Across all five countries, unit prices are heavily concentrated under 2 units, with median values staying consistent around 1.5–2.0.
  • The United Kingdom exhibits a distinct multimodal/spiky price distribution, indicating specific, repetitive price points compared to the smoother density distributions of the other nations.
  • EIRE shows a slightly higher interquartile range and a thicker distribution curve between 2 and 5 units, reflecting a marginally higher overall unit price spread.

Revenue By Country¶

In [21]:
import plotly.express as px

# Aggregate by country
country_sales = df_clean.groupby('Country')['Revenue'].sum().reset_index()

# Plot map
fig = px.choropleth(
    country_sales,
    locations='Country',
    locationmode='country names',
    color='Revenue',
    color_continuous_scale='Blues',
    title='Revenue by Country'
)

fig.show()

Conclusion :¶

Hover over any country on the interactive map to view detailed revenue insights. This map feature uses color intensity to represent revenue metrics, clearly highlighting top-performing regions like the UK at a glance.

And that brings us to the end of the Data Visualization phase. 📊

We started with raw data, explored patterns, identified trends, and turned numbers into stories that can actually be understood.

But visualization answers “What is happening?” The next question is — “What can we predict?” 🔍

🚀 Next up: Machine Learning — where data starts making predictions.

The journey from Messy to Meaningful continues…